﻿using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;

namespace Beeflys.Generator
{
    static class Protocol
    {
        private const string TASK_COMMON_NAME = "Task<>";
        private const string TASK_COMMON_FULLNAME = "System.Threading.Tasks.Task<>";
        
        

        private static List<Error> errors = new List<Error>();
        private static List<UTypeInfo> protocols = new List<UTypeInfo>();
        
        public static (List<UTypeInfo>, List<Error>) Start(List<UTypeInfo> types)
        {
            foreach (var item in types)
            {
                if (!item.IsInterface) continue;
                if (item.Attributes.Count < 1) continue;

                foreach (var attribute in item.Attributes)
                {
                    if (attribute.FullName == MessengerType.ACTOR_MESSENGER_DECLARE_ATTRIBUTE_NAME ||
                        attribute.FullName == MessengerType.ACTOR_MESSENGER_DECLARE_ATTRIBUTE_FULLNAME)
                    {
                        var interfaces = item.GetBaseInterfaces();

                        if (interfaces.Count > 0)
                        {
                            if (!(interfaces.Count == 1 && interfaces[0].FullName == MessengerType.I_ACTOR_MESSENGER_DEFINE))
                            {
                                errors.Add(GetError(item.FullName, $"Only interface {MessengerType.I_ACTOR_MESSENGER_DEFINE} can be inherited"));
                            }
                        }
                        else
                        {
                            errors.Add(GetError(item.FullName, $"Only interface {MessengerType.I_ACTOR_MESSENGER_DEFINE} can be inherited"));
                        }
                        
                        if (item.Propertys.Count > 0)
                        {
                            errors.Add(GetError(item.FullName,"cannot declare a property"));
                        }

                        if (item.Modifiers != Accessibility.Public)
                        {
                            errors.Add(GetError(item.FullName, "must use the public modifier"));
                        }

                        foreach (var method in item.Methods)
                        {
                            MethodParse(method);
                        }
                        
                        if (errors.Count < 1)
                        {
                            protocols.Add(item);
                        }
                    }
                }
            }

            return (protocols, errors);
        }


        private static void MethodParse(UMethodInfo methodInfo)
        {
            string methodName = methodInfo.FullName;

            if (methodInfo.Modifiers != Accessibility.Public)
            {
                errors.Add(GetError("",$"method {methodName} The public modifier must be used"));
            }

            if (methodInfo.IsGenericMethod)
            {
                errors.Add(GetError("",$"method {methodName} It cannot be a generic method"));
            }

            if (!methodInfo.ReturnVoid)
            {
                var returnType = methodInfo.GetReturnType();
                if (!(returnType.FullName == "Task" || returnType.FullName == "System.Threading.Tasks.Task"))
                {
                    var returnOriginalType = methodInfo.GetReturnType().GetGenericOriginalType();
                
                    if (returnOriginalType.FullName ==  TASK_COMMON_NAME ||
                        returnOriginalType.FullName == TASK_COMMON_FULLNAME)
                    {
                        MethodReturnValueParse(methodInfo);
                    }
                    else
                    {
                        errors.Add(GetError("",$"method {methodName} The return value must be void or Task, Task<>"));
                    }
                }
            }

            MethodArgsParse(methodInfo);
        }

        private static void MethodReturnValueParse(UMethodInfo methodInfo)
        {
            var args = methodInfo.GetReturnType().GetGenericArguments();
            foreach (var item in args)
            {
                if (item.IsTupleType)
                {
                    errors.Add(GetError("",$"method {methodInfo.FullName} The return value does not support tuples"));
                }
                else
                {
                    if (!IsBaseType(item))
                    {
                        if (!IsValidType(item))
                        {
                            errors.Add(GetError("",$"The return value of method {methodInfo.FullName} does not support abstract classes, interfaces, tuples, or generics"));
                        }
                    }
                }
            }
        }

        private static void MethodArgsParse(UMethodInfo methodInfo)
        {
            foreach (var item in methodInfo.GetArgsTypes())
            {
                if (!IsBaseType(item))
                {
                    if (!IsValidType(item))
                    {
                        errors.Add(GetError("", $"Method {methodInfo.FullName} args do not support abstract classes, interfaces, tuples, or generics"));
                    }
                }
            }
        }
                
        private static bool IsValidType(UTypeInfo typeInfo)
        {
            if (typeInfo.IsAbstract || typeInfo.IsInterface || typeInfo.IsTupleType || typeInfo.IsGenericType)
            {
                return false;
            }

            return true;
        }

        
        private static bool IsBaseType(UTypeInfo typeInfo)
        {
            return !string.IsNullOrEmpty(GetBaseTypeText(typeInfo));
        }
        
        private static string GetBaseTypeText(UTypeInfo typeInfo)
        {
            if (typeInfo.IsBaseTypeKeyword())
            {
                return typeInfo.GetTypeFullNameFromKeyword();
            }

            if (typeInfo.IsBaseType())
            {
                return typeInfo.FullName;
            }
            
            return string.Empty;
        }

        private static Error GetError(string name, string text)
        {
            var message = $"The interface {name} of {MessengerType.ACTOR_MESSENGER_DECLARE_ATTRIBUTE_FULLNAME} is marked {text}";
            return new Error(message);
        }
    }
}